﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;

namespace VideoFlowTagger
{
	/// <summary>
	/// As the name suggests, this class represents the metadata inside of the MP4, M4V file. 
	/// </summary>
    public class iTunesMetadata
    {
		/// <summary>
		/// Gets or sets the artist.
		/// </summary>
		/// <value>
		/// The artist.
		/// </value>
        [AtomicParsleyTag(" --artist ")]
        [AtomTag("©ART")]
        public String Artist { get; set; }
		
		/// <summary>
		/// Gets or sets the title.
		/// </summary>
		/// <value>
		/// The title.
		/// </value>
        [AtomicParsleyTag(" --title ")]
        [AtomTag("©nam")]
        public String Title { get; set; }
		
		/// <summary>
		/// Gets or sets the album.
		/// </summary>
		/// <value>
		/// The album.
		/// </value>
        [AtomicParsleyTag(" --album ")]
        [AtomTag("©alb")]
        public String Album { get; set; }
		
		/// <summary>
		/// Gets or sets the genre.
		/// </summary>
		/// <value>
		/// The genre.
		/// </value>
        [AtomicParsleyTag(" --genre ")]
        [AtomTag("gnre")]
        public GenreEnum? Genre { get; set; }
		
		/// <summary>
		/// Gets or sets the custom genre.
		/// </summary>
		/// <value>
		/// The custom genre.
		/// </value>
		[AtomicParsleyTag(" --genre ")]
        [AtomTag("©gen")]
		public String CustomGenre { get; set; }
		
		/// <summary>
		/// Gets or sets the track number (or track number/total tracks).
		/// </summary>
		/// <value>
		/// The track number (or track number/total tracks).
		/// </value>
        [AtomicParsleyTag(" --tracknum ")]
        [AtomTag("trkn")]
        public NumberTotal? Tracknum { get; set; }
		
		/// <summary>
		/// Gets or sets the disk number (or disk number/total disks).
		/// </summary>
		/// <value>
		/// The disk number (or disk number/total disks).
		/// </value>
        [AtomicParsleyTag(" --disk ")]
        [AtomTag("disk")]
        public NumberTotal? Disk { get; set; }
		
		/// <summary>
		/// Gets or sets the comment.
		/// </summary>
		/// <value>
		/// The comment.
		/// </value>
        [AtomicParsleyTag(" --comment ")]
        [AtomTag("©cmt")]
        public String Comment { get; set; }
		
		/// <summary>
		/// Gets or sets the release date (e.g. 2010-08-27T09:00:00Z).
		/// </summary>
		/// <value>
		/// The release date (e.g. 2010-08-27T09:00:00Z).
		/// </value>
        [AtomicParsleyTag(" --year ")]
        [AtomTag("©day")]
        public UTC? ReleaseDate { get; set; }
		
		/// <summary>
		/// Gets or sets the lyrics.
		/// </summary>
		/// <value>
		/// The lyrics.
		/// </value>
        [AtomicParsleyTag(" --lyrics ")]
        [AtomTag("©lyr")]
        public String Lyrics { get; set; }
		
		/// <summary>
		/// Gets or sets the composer.
		/// </summary>
		/// <value>
		/// The composer.
		/// </value>
        [AtomicParsleyTag(" --composer ")]
        [AtomTag("©wrt")]
        public String Composer { get; set; }
		
		/// <summary>
		/// Gets or sets the copyright.
		/// </summary>
		/// <value>
		/// The copyright.
		/// </value>
        [AtomicParsleyTag(" --copyright ")]
        [AtomTag("cprt")]
        public String Copyright { get; set; }
		
		/// <summary>
		/// Gets or sets the grouping.
		/// </summary>
		/// <value>
		/// The grouping.
		/// </value>
        [AtomicParsleyTag(" --grouping ")]
        [AtomTag("©grp")]
        public String Grouping { get; set; }
        
		/// <summary>
		/// Gets or sets the path to the artwork file (jpg or png).
		/// </summary>
		/// <value>
		/// The path to the artwork file (jpg or png).
		/// </value>
        [AtomicParsleyTag(" --artwork ")]
        [AtomTag("covr")]
        public String Artwork { get; set; }
		
		/// <summary>
		/// Gets or sets the tempo/bpm.
		/// </summary>
		/// <value>
		/// The tempo/bpm.
		/// </value>
        [AtomicParsleyTag(" --bpm ")]
        [AtomTag("tmpo")]
        public Int16? Bpm { get; set; }
		
		/// <summary>
		/// Gets or sets the album artist.
		/// </summary>
		/// <value>
		/// The album artist.
		/// </value>
        [AtomicParsleyTag(" --albumArtist ")]
        [AtomTag("aART")]
        public String AlbumArtist { get; set; }
		
		/// <summary>
		/// Gets or sets the compilation flag.
		/// </summary>
		/// <value>
		/// The compilation flag.
		/// </value>
        [AtomicParsleyTag(" --compilation ")]
        [AtomTag("cpil")]
        public Boolean? Compilation { get; set; }
		
		/// <summary>
		/// Gets or sets the HD video flag.
		/// </summary>
		/// <value>
		/// The HD video flag.
		/// </value>
        [AtomicParsleyTag(" --hdvideo ")]
        [AtomTag("hdvd")]
		public Boolean? HDVideo { get; set; }
		
		/// <summary>
		/// Gets or sets the advisory (clean or explicit).
		/// </summary>
		/// <value>
		/// The advisory (clean or explicit).
		/// </value>
        [AtomicParsleyTag(" --advisory ")]
        [AtomTag("rtng")]
        public String Advisory { get; set; }
		
		/// <summary>
		/// Gets or sets the content type (e.g. Movie).
		/// </summary>
		/// <value>
		/// The content type (e.g. Movie).
		/// </value>
        [AtomicParsleyTag(" --stik ")]
        [AtomTag("stik")]
        public TypeEnum? Type { get; set; }
		
		/// <summary>
		/// Gets or sets the description.
		/// </summary>
		/// <value>
		/// The description.
		/// </value>
        [AtomicParsleyTag(" --description ")]
        [AtomTag("desc")]
        public String Description { get; set; }
		
		/// <summary>
		/// Gets or sets the long description.
		/// </summary>
		/// <value>
		/// The long description.
		/// </value>
        [AtomicParsleyTag(" --longdesc ")]
        [AtomTag("ldes")]
        public String LongDescription { get; set; }
		
		/// <summary>
		/// Gets or sets the TV network.
		/// </summary>
		/// <value>
		/// The TV network.
		/// </value>
        [AtomicParsleyTag(" --TVNetwork ")]
        [AtomTag("tvnn")]
        public String TVNetwork { get; set; }
		
		/// <summary>
		/// Gets or sets the name of the TV show.
		/// </summary>
		/// <value>
		/// The name of the TV show.
		/// </value>
        [AtomicParsleyTag(" --TVShowName ")]
        [AtomTag("tvsh")]
        public String TVShowName { get; set; }
		
		/// <summary>
		/// Gets or sets the TV episode identifier (e.g. 201).
		/// </summary>
		/// <value>
		/// The TV episode identifier (e.g. 201).
		/// </value>
        [AtomicParsleyTag(" --TVEpisode ")]
        [AtomTag("tves")]
        public Int16? TVEpisodeId { get; set; }
		
		/// <summary>
		/// Gets or sets the TV episode number.
		/// </summary>
		/// <value>
		/// The TV episode number.
		/// </value>
        [AtomicParsleyTag(" --TVEpisodeNum ")]
        [AtomTag("tven")]
        public Int16? TVEpisodeNum { get; set; }
		
		/// <summary>
		/// Gets or sets the TV season number.
		/// </summary>
		/// <value>
		/// The TV season number.
		/// </value>
        [AtomicParsleyTag(" --TVSeasonNum ")]
        [AtomTag("tvsn")]
        public Int16? TVSeasonNum { get; set; }
		
		/// <summary>
		/// Gets or sets the podcast flag.
		/// </summary>
		/// <value>
		/// The podcast flag.
		/// </value>
        [AtomicParsleyTag(" --podcastFlag ")]
        [AtomTag("pcst")]
        public Boolean? PodcastFlag { get; set; }
		
		/// <summary>
		/// Gets or sets the podcast category.
		/// </summary>
		/// <value>
		/// The podcast category.
		/// </value>
        [AtomicParsleyTag(" --category ")]
        [AtomTag("catg")]
        public String PodcastCategory { get; set; }
		
		/// <summary>
		/// Gets or sets the podcast keyword.
		/// </summary>
		/// <value>
		/// The podcast keyword.
		/// </value>
        [AtomicParsleyTag(" --keyword ")]
        [AtomTag("keyw")]
        public String PodcastKeyword { get; set; }
		
		/// <summary>
		/// Gets or sets the podcast URL.
		/// </summary>
		/// <value>
		/// The podcast URL.
		/// </value>
        [AtomicParsleyTag(" --podcastURL ")]
        [AtomTag("purl")]
        public String PodcastUrl { get; set; }
		
		/// <summary>
		/// Gets or sets the podcast GUI.
		/// </summary>
		/// <value>
		/// The podcast GUI.
		/// </value>
        [AtomicParsleyTag(" --podcastGUID ")]
        [AtomTag("egid")]
        public String PodcastGUID { get; set; }
		
		/// <summary>
		/// Gets or sets the purchase date.
		/// </summary>
		/// <value>
		/// The purchase date.
		/// </value>
        [AtomicParsleyTag(" --purchaseDate ")]
        [AtomTag("purd")]
        public UTC? PurchaseDate { get; set; }
		
		/// <summary>
		/// Gets or sets the name of the Person/company who encoded the file.
		/// </summary>
		/// <value>
		/// The name of the Person/company who encoded the file.
		/// </value>
        [AtomicParsleyTag(" --encodingBy ")]
        [AtomTag("©enc")]
        public String EncodingBy { get; set; }
		
		/// <summary>
		/// Gets or sets the encoding tool.
		/// </summary>
		/// <value>
		/// The encoding tool.
		/// </value>
        [AtomicParsleyTag(" --encodingTool ")]
        [AtomTag("©too")]
        public String EncodingTool { get; set; }
		
		/// <summary>
		/// Gets or sets the account name.
		/// </summary>
		/// <value>
		/// The ccount name.
		/// </value>
        [AtomicParsleyTag(" --apID ")]
        [AtomTag("apID")]
        public String AccountName { get; set; }
		
		/// <summary>
		/// Gets or sets the iTunes Catalog ID.
		/// </summary>
		/// <value>
		/// The iTunes Catalog ID.
		/// </value>
        [AtomicParsleyTag(" --cnID ")]
        [AtomTag("cnID")]
        public Int16? CatalogID { get; set; }
		
		/// <summary>
		/// Gets or sets the gapless flag.
		/// </summary>
		/// <value>
		/// The gapless flag.
		/// </value>
        [AtomicParsleyTag(" --gapless ")]
        [AtomTag("pgap")]
        public Boolean? Gapless { get; set; }
		
		/// <summary>
		/// Gets or sets the tv/mpaa rating.
		/// </summary>
		/// <value>
		/// The gapless tv/mpaa rating.
		/// </value>
        [AtomicParsleyTag(" --rDNSatom ")]
        [AtomTag("extc")]
        public String ContentRating { get; set; }
		
		/// <summary>
		/// Gets or sets the cast.
		/// </summary>
		/// <value>
		/// The cast.
		/// </value>
		[AtomicParsleyTag("")]
        [AtomTag("cast")]
		public Person? Cast { get; set; }
		
		/// <summary>
		/// Gets or sets the directors.
		/// </summary>
		/// <value>
		/// The directors.
		/// </value>
        [AtomicParsleyTag("")]
        [AtomTag("dire")]
		public Person? Directors { get; set; }
		
		/// <summary>
		/// Gets or sets the screenwriters.
		/// </summary>
		/// <value>
		/// The screenwriters.
		/// </value>
        [AtomicParsleyTag("")]
        [AtomTag("scre")]
		public Person? Screenwriters { get; set; }
		
		/// <summary>
		/// Gets or sets the producers.
		/// </summary>
		/// <value>
		/// The producers.
		/// </value>
        [AtomicParsleyTag("")]
        [AtomTag("prod")]
		public Person? Producers { get; set; }


        public void SetValue(String atom, Object value)
        {
            PropertyInfo[] properties = this.GetType().GetProperties();

            foreach (PropertyInfo p in properties)
            {
                String tagName = (p.GetCustomAttributes(typeof(AtomTagAttribute), false)[0] as AtomTagAttribute).Value;
                if (tagName == atom)
                {
                    Type type = p.PropertyType;

                    if (type.FullName.Contains("UTC"))
                    {
                        p.SetValue(this, new UTC(value.ToString()), null);
                    }
                    else if (type.FullName.Contains("NumberTotal"))
                    {
                        p.SetValue(this, new NumberTotal(value.ToString()), null);
                    }
                    else if (type.FullName.Contains("Int16"))
                    {
                        short i;
                        Int16.TryParse(value.ToString(), out i);
                        p.SetValue(this, i, null);
                    }
					else if (tagName == "hdvd")
					{
						// Special case
						bool b = Convert.ToBoolean(Convert.ToInt16(value));
						p.SetValue(this, b, null);
					}
                    else if (type.FullName.Contains("Boolean"))
                    {
						bool b;
						Boolean.TryParse(value.ToString(), out b);
                        p.SetValue(this, b, null);
                    }
                    else if (type.FullName.Contains("TypeEnum"))
                    {
						String typeEnum = value.ToString();
						if (typeEnum == "Movie")
							p.SetValue(this, TypeEnum.Movie, null);
						else if (typeEnum == "Normal")
							p.SetValue(this, TypeEnum.Movie, null);
						else if (typeEnum == "Audiobook")
							p.SetValue(this, TypeEnum.Audiobook, null);
						else if (typeEnum == "Whacked Bookmark")
							p.SetValue(this, TypeEnum.Whacked_Bookmark, null);
						else if (typeEnum == "Music Video")
							p.SetValue(this, TypeEnum.Music_Video, null);
						else if (typeEnum == "Short Flim")
							p.SetValue(this, TypeEnum.Short_Flim, null);
						else if (typeEnum == "TV Show")
							p.SetValue(this, TypeEnum.TV_Show, null);
						else if (typeEnum == "Booklet")
							p.SetValue(this, TypeEnum.Booklet, null);
                    }
                    else if (type.FullName.Contains("Person"))
                    {
						String title = "";
						if (atom == "dire")
							title = "directors";
						else if (atom == "scre")
							title = "screenwriters";
						else if (atom == "prod")
							title = "producers";
						else if (atom == "cast")
							title = "cast";
                        Person person = new Person(title, value as List<String>);
                        p.SetValue(this, person, null);
                    }
                    else
                    {
                        p.SetValue(this, value, null);
                    }
                    break;
                }
            }
        }

        public override string ToString()
        {
            PropertyInfo[] properties = this.GetType().GetProperties();
            List<String> result = new List<string>();
			
            foreach (PropertyInfo p in properties)
            {
				// Get property value
                var tagValue = p.GetValue(this, null);
                if (tagValue != null)
                {
					// Get iTunes Metadata tag
                    string tagName = (p.GetCustomAttributes(typeof(AtomicParsleyTagAttribute), false) as AtomicParsleyTagAttribute[])[0].Value;
					
					// DO NOT CHANGE THE ORDER
					// Process reversed DNS atom first
					// In this case this would be the rating only
					// Producers, cast, screenwriters, directors will be processed later
					if (tagName.Contains("--rDNSatom"))
						result.Add(tagName + tagValue + " ");
					
					// These atoms use single-quote for data
					else if (tagName.Contains("--description") ||
							 tagName.Contains("--longdesc") ||
							 tagName.Contains("--lyrics") ||
							 tagName.Contains("--comment"))
						result.Add(tagName + "'" + tagValue + "' ");
                    
					// These atoms should use double-quote
					else if (tagValue is string || tagValue is Enum)	
                    	result.Add(tagName + "\"" + tagValue + "\" ");
					
					// Boolean atoms cast to lower case
					else if (tagValue is Boolean)
						result.Add(tagName + tagValue.ToString().ToLower());
					
					// Ignore if it's producers, cast, screenwriters, directors
					else if (tagValue is Person)
						continue;
					
					// Process as normal with other
                    else
                        result.Add(tagName + tagValue + " ");
                }
            }
			// SPECIAL CASE
			// These will be combined as XML and attache to one atom
			if (Cast != null && Directors != null && Screenwriters != null && Producers != null)
				result.Add(CreatePersonXML());

            String resultString = string.Join(null, result.ToArray());
            return resultString;
        }
		
		/// <summary>
		/// Creates XML for producers, cast, screenwriters, and directors.
		/// </summary>
		/// <returns>
		/// The XML for producers, cast, screenwriters, and directors.
		/// </returns>
		public String CreatePersonXML()
		{
			String result = " --rDNSatom '<?xml version=\"1.0\" encoding=\"UTF-8\"?> ";
			result += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"> ";
			result += "<plist version=\"1.0\"> <dict> ";
			if (Cast != null)
				result += Cast.ToString();
			if (Directors != null)
				result += Directors.ToString();
			if (Screenwriters != null)
				result += Screenwriters.ToString();
			if (Producers != null)
				result += Producers.ToString();
			result += "</dict> </plist>' name=iTunMOVI domain=com.apple.iTunes ";
			
			return result;
		}
    }

}
